/*
 * oppzip.cpp
 *
 *  Created on: Jul 9, 2013
 *      Author: x
 */
#include <cstdio>
#include <zlib.h>
#include <cassert>
#include "oppzip.h"
#include <cstdlib>
#include <cstring>
#include <iostream>
using namespace std;

namespace zopp {

const int CHUNK = 16384;

int inf(FILE *source, FILE *dest)
{
	int ret;
	unsigned have;
	z_stream strm;
	char in[CHUNK];
	char out[CHUNK];

	/* allocate inflate state */
	strm.zalloc = Z_NULL;
	strm.zfree = Z_NULL;
	strm.opaque = Z_NULL;
	strm.avail_in = 0;
	strm.next_in = Z_NULL;
	ret = inflateInit(&strm);
	if (ret != Z_OK)
		return ret;

	/* decompress until deflate stream ends or end of file */
	do {
		strm.avail_in = fread(in, 1, CHUNK, source);
		if (ferror(source)) {
			(void)inflateEnd(&strm);
			return Z_ERRNO;
		}
		if (strm.avail_in == 0)
			break;
		strm.next_in = (Bytef *) in;

		/* run inflate() on input until output buffer not full */
		do {
			strm.avail_out = CHUNK;
			strm.next_out = (Bytef *) out;
			ret = inflate(&strm, Z_NO_FLUSH);
			assert(ret != Z_STREAM_ERROR);  /* state not clobbered */
			switch (ret) {
			case Z_NEED_DICT:
				ret = Z_DATA_ERROR;     /* and fall through */
				break;
			case Z_DATA_ERROR:
			case Z_MEM_ERROR:
				(void)inflateEnd(&strm);
				return ret;
			}
			have = CHUNK - strm.avail_out;
			if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
				(void) inflateEnd(&strm);
				return Z_ERRNO;
			}
		} while (strm.avail_out == 0);
		assert(strm.avail_in == 0);     /* all input will be used */

		/* done when inflate() says it's done */
	} while (ret != Z_STREAM_END);

	/* clean up and return */
	(void) inflateEnd(&strm);
	return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
}

const char *ENERGY_TABLE = "energies.opp";
const int USERERR = -1;

int load_energies(EMATRIX *ematrix, const string &dir, int verbose)
/* this program returns number of conformers loaded, or -1 if no exsiting energy table */
{
	int i, n_conf;
    char sbuff[128];
    FILE *fp, *fp2;
    CONF_HEAD *conf;
    PAIRWISE *pw;
    char version[128];

    /* Obtain the first line of the energy lookup table, which is the number of conformers */
    sprintf(sbuff, "%s/%s", dir.c_str(), ENERGY_TABLE);
    if (!(fp = fopen(sbuff, "r"))) {
        printf("energies.opp not found\n");
        return -1;
    }

    // unzip the energies.opp
    fp2 = tmpfile();
    inf(fp, fp2);
    rewind(fp2);
    fclose(fp);


    fgets(sbuff, sizeof(sbuff), fp2);
    if (sscanf(sbuff, "%d %s", &n_conf, version) != 2) {
       printf("    Opp file Version mismatch\n");
       return USERERR;
    }

    /* allocate memeory */
    if (ematrix->n > 0) {  /* existing table */
       if (ematrix->n != n_conf) {
          printf("error in loading Energy lookup table size %d on to %d\n", n_conf, ematrix->n);
          return USERERR;
       }
    }
    else {
       if (!(ematrix->conf = (CONF_HEAD *) calloc(n_conf, sizeof(CONF_HEAD)))) {
          printf("   Memory error in A load_energies\n");
          return 0; /* none loaded and memory cleared */
       }
       if (!(ematrix->pw = (PAIRWISE **) calloc(n_conf, sizeof(PAIRWISE *)))) {
          printf("   Memory error in B load_energies\n");
          return 0; /* none loaded and memory cleared */
       }
       for (i=0; i<n_conf; i++) {
          if (!(ematrix->pw[i] = (PAIRWISE *) calloc(n_conf, sizeof(PAIRWISE)))) {
             printf("   Memory error in C load_energies\n");
             return 0; /* none loaded and memory cleared */
          }
       }
       ematrix->n = n_conf;
    }

    /* Buffer of head part */
    if (!(conf = (CONF_HEAD *) calloc(n_conf, sizeof(CONF_HEAD)))){
          printf("   Memory error loading head in load_energies\n");
          return USERERR;
    }
    if (!(pw = (PAIRWISE *) calloc(n_conf, sizeof(PAIRWISE)))){
          printf("   Memory error loading PW buffer in load_energies\n");
          return USERERR;
    }


    if (((int) fread(conf, sizeof(CONF_HEAD), ematrix->n, fp2)) != n_conf) {
        printf("   Error in loading pairwise interaction headers at position %d\n", i);
        return USERERR;
    }

    cout << ematrix->n << " conformers in load_energies" << endl;
    for (i=0; i<ematrix->n; i++) {
        fread(pw, sizeof(PAIRWISE), ematrix->n, fp2);
		if ( verbose == 1 ) {
			printf("reading row %05d\r", i); fflush(stdout);
		}

        if (conf[i].on == 't') {
           memcpy(&ematrix->conf[i], &conf[i], sizeof(CONF_HEAD));
           memcpy(ematrix->pw[i], pw, ematrix->n*sizeof(PAIRWISE));
        }
        if (ematrix->conf[i].on != 't') /* initilize even if not a valid delphi run */
           memcpy(&ematrix->conf[i], &conf[i], sizeof(CONF_HEAD));
    }
    if ( verbose == 1) {
		printf("\n"); fflush(stdout);
    }

    free(conf);
    free(pw);

    fclose(fp2);

    return n_conf;
}

int free_ematrix(EMATRIX *ematrix)
{
	int i;

	for (i=0; i<ematrix->n; i++) {
		free(ematrix->pw[i]);
	}
	if (ematrix->n > 0) {
		free(ematrix->pw);
		free(ematrix->conf);
	}
	ematrix->n = 0;

	return 0;
}
}
